package com.example.concurrent;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.IntStream;

/**
 * @author houyangfan
 * @version 1.0
 * @date 2022/7/6 19:20
 */
@RestController
@Slf4j
public class SynchronizedBug {

    /**
     * 示例1
     * 该示例是演示在不加锁的情况下，add方法循环一万次对a和b进行++ 操作，compare方法进行比较ab的值，是否会出现a<b的情况，
     * 如果存在该情况则输出对应的值。 结果： 在不加锁的情况下，会出现a < b的情况，并且还会打印出a > b 为 true
     * <p>
     * 问题分析：
     * 是因为两个线程是交错执行 add 和 compare 方法中的业务逻辑，而且这些业务逻辑不是原子性的：a++ 和 b++ 操作中可以穿插在 compare 方法的比较代码中；
     * <p>
     * 如果只对add 方法加锁也是不管用的 以下是对synchronized 对类成员变量的访问解释：
     * 每个类实例对应一把锁，每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行，否则所属线程阻塞，方法一旦执行，就独占该锁，
     * 直到从该方法返回时才将锁释放，此后被阻塞的线程方能获得该锁，重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例，
     * 其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态（因为至多只有一个能够获得该类实例对应的锁）
     * 所以 为add 方法 加锁，compare 还是可以访问到ab属性的值。
     * <p>
     * 具体讲解：https://blog.csdn.net/LONG_Yi_1994/article/details/81411867
     * <p>
     * 解决:
     * 1. 可以为 add() 和 compare() 都加上synchronized
     * 2. 可以在两个方法中 每次进行和 ab相关的操作的时候 加都加上synchronized代码快。
     */
    volatile int a = 1;
    volatile int b = 1;
    //public Object lockObject = new Object();

    public synchronized void add() {
        System.out.println("add start");
        for (int i = 0; i < 10000; i++) {
            //synchronized (lockObject) {
            a++;
            b++;
            //}
        }
        System.out.println("add done");
    }

    public synchronized void compare() {
        System.out.println("compare start");
        //synchronized (lockObject) {
        for (int i = 0; i < 10000; i++) {
            if (a < b) {
                log.error("a:{},b:{},{}", a, b, a > b);
            }
        }
        //}
        System.out.println("compare done");
    }

    /**
     * 示例2
     * 该示例用于测试 在非静态方法上加锁，起10万个线程后，静态变量count每次++后却不是10万
     * <p>
     * 解决方法： 可以定义一个静态类型的对象，该对象可以作为加锁的对象，这时候整个类的静态变量都会只有在有锁的情况下操作。
     */

    @Getter
    public static int count = 0;
    public static Object lockObject = new Object();

    public static int reset() {
        count = 0;
        return count;
    }

    public static int getCount() {
        return count;
    }

    public void wrong() {
        synchronized (lockObject) {
            count++;
        }
    }

    public static void testWrong() {
        SynchronizedBug.reset();
        IntStream.rangeClosed(1, 100000).parallel().forEach(i -> new SynchronizedBug().wrong());
        System.out.println(SynchronizedBug.getCount());
    }

    /**
     * 示例3
     * 用锁的时候要考虑锁粒度和使用场景的问题
     * <p>
     * 问题：比如，在业务代码中，有一个 ArrayList 因为会被多个线程操作而需要保护，又有一段比较耗时的操作（代码中的 slow 方法）
     * 不涉及线程安全问题，应该如何加锁呢？
     */

    private List<Integer> data = new ArrayList<>();

    private void slow() {
        try {
            // 休眠10ms
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //错误的加锁方式
    public int wrong2() {
        long begin = System.currentTimeMillis();
        IntStream.rangeClosed(1, 1000).parallel().forEach(i -> {
            //加锁粒度太粗了
            synchronized (this) {
                slow();
                data.add(i);
            }
        });
        log.info("took:{}", System.currentTimeMillis() - begin);
        return data.size();
    }

    //正确的加锁方式
    public int wrong3() {
        long begin = System.currentTimeMillis();
        IntStream.rangeClosed(1, 1000).parallel().forEach(i -> {
            slow();
            synchronized (this) {
                data.add(i);
            }
        });
        log.info("took:{}", System.currentTimeMillis() - begin);
        return data.size();
    }

    /**
     * 示例4
     */
    class ReentrantLockList {

        public List<Integer> list = new ArrayList<>();

        private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        private final Lock readLock = reentrantReadWriteLock.readLock();
        private final Lock writeLock = reentrantReadWriteLock.writeLock();


        // 添加元素
        public void add(Integer e) {
            // 加锁
            writeLock.lock();
            try {
                // 添加元素
                list.add(e);
            } finally {
                // 最后释放锁
                writeLock.unlock();
            }
        }

        // 删除元素
        public void remove(Integer e) {
            writeLock.lock();
            try {
                list.remove(e);
            } finally {
                writeLock.unlock();
            }
        }

        // 获取数据
        public Integer get(int index) {
            // 加读锁
            readLock.lock();
            try {
                return list.get(index);
            } finally {
                readLock.unlock();
            }
        }

        // size
        public Integer size() {
            // 加读锁
            readLock.lock();
            try {
                return list.size();
            } finally {
                readLock.unlock();
            }
        }

    }

    public static void main(String[] args) {

        // 示例1
        /*SynchronizedBug synchronizedBug = new SynchronizedBug();
        new Thread(() -> synchronizedBug.add()).start();
        new Thread(() -> synchronizedBug.compare()).start();*/
        //示例2
        testWrong();

        //示例3
        //System.out.println(new SynchronizedBug().wrong2());
        //System.out.println(new SynchronizedBug().wrong3());

        // 示例四
        /*ReentrantLockList reentrantLockList = new SynchronizedBug().new ReentrantLockList();
        IntStream.rangeClosed(1, 1000).parallel().forEach(i -> {
            reentrantLockList.add(i);
            log.error("第i个元素为：{}", i, reentrantLockList.get(i));
        });
        System.out.println(reentrantLockList.size());*/
    }

}
